﻿
Imports System.Threading

'multi writer / multi reader model
'lock / unlock is for writer, which will also have a sequentialLock to protect.
'wait / release is for reader, suppose reader will not change, but only read, so no protection needed.
'readers will not block other readers, except for fl check & sl increment / decrement
'writers will block other writers, meanwhile any readers
Public Class duallock
    Private fl As sequentialLock = Nothing
    Private sl As Int32 = 0

    Public Sub New()
        fl = New sequentialLock()
        sl = 0
    End Sub

    Public Sub lock()
        fl.wait()
        While sl > 0
            sleep(1)
        End While
    End Sub

    Public Sub unlock()
        assert(sl = 0)
        fl.release()
    End Sub

    Public Sub wait()
        fl.wait()
        Interlocked.Increment(sl)
        fl.release()
    End Sub

    Public Sub release()
        Interlocked.Decrement(sl)
    End Sub

    'suppose test_string will not changed during any reader threads
    Private Shared test_string As String = Nothing
    Private Shared l As duallock = Nothing
    Private Shared round As Int64 = 1024

    Private Shared Sub _sleep()
        sleep(rnd(0, 100, True))
    End Sub

    Private Shared Sub writer()
        Dim r As Int64 = 0
        r = round
        'keep the thread overall time almost the same
        While r > 0
            If rnd(0, 1) < 0.2 Then
                l.lock()
                _sleep()
                test_string = createGUID()
                raiseError("change test_string to " + test_string)
                _sleep()
                l.unlock()
            Else
                _sleep()
                _sleep()
            End If
            r -= 1
        End While
    End Sub

    Private Shared Sub reader()
        Dim r As Int64 = 0
        r = round
        While r > 0
            l.wait()
            Dim s As String = Nothing
            copy(s, test_string)
            raiseError("read test_string " + s)
            _sleep()
            assert(strsame(s, test_string))
            _sleep()
            assert(strsame(s, test_string))
            l.release()
            r -= 1
        End While
    End Sub

    Public Shared Sub unittest()
        Dim threadcount As Int64 = 0
        threadcount = 8
        If isreleasebuild() Then
            threadcount <<= 2
            round <<= 2
        End If
        l = New duallock()
        raiseError("********** duallock Automatical Unittest ***********", errorHandle.errorType.application)
        Dim threads((threadcount << 1) - 1) As Thread
        For i As Int64 = 0 To (threadcount >> 1) - 1
            threads(i) = startThread(AddressOf writer)
        Next
        For i As Int64 = (threadcount >> 1) To (threadcount << 1) - 1
            threads(i) = startThread(AddressOf reader)
        Next

        For i As Int64 = 0 To threads.Length() - 1
            assert(Not threads(i) Is Nothing)
            threads(i).Join()
        Next
        raiseError("******* finish duallock Automatical Unittest *******", errorHandle.errorType.application)
    End Sub
End Class
